/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019.
 * Description: support hungtask enhance feature
 * Author: chenshiyan <chenshiyan@huawei.com>
 * Create: 2018-08-28
 */

#include <linux/ilockdep.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/task_struct_extend.h>
#include <linux/module.h>
#include <linux/siphash.h>
#include <linux/rtos_sysctl.h>

struct kmem_cache *ilockdep_cachep;
struct ilockdep ilockdep_init_task;

#define FORMAT_LOCKING "locking:\n%-10p(hash) %-16s\t\n"
#define FORMAT_LOCKED "%-10p(hash) %-16s %-2d [<%p(hash)>] %pS\t\n"

void ilockdep_init(struct ilockdep *dep)
{
	memset(dep, 0, sizeof(struct ilockdep));
}

void ilockdep_clear_locking(struct task_struct *p)
{
	struct rtos_task_struct *rtos_tsk = task_to_rtos_task(p);

	rtos_tsk->ilockdep_lock->locking = NULL;
	rtos_tsk->ilockdep_lock->locking_name = NULL;
}

void ilockdep_acquire(struct ilockdep_map *lock, unsigned long ip, void *lock_addr)
{
	struct rtos_task_struct *rtos_tsk = task_to_rtos_task(current);

	rtos_tsk->ilockdep_lock->locking_name = lock->name;
	rtos_tsk->ilockdep_lock->locking = lock_addr;
}

void ilockdep_acquired(struct ilockdep_map *lock, unsigned long ip, void *lock_addr)
{
	unsigned int i;
	unsigned long flags;
	struct task_struct *curr = current;
	struct rtos_task_struct *rtos_tsk = task_to_rtos_task(curr);

	raw_local_irq_save(flags);
	ilockdep_clear_locking(curr);
	if (rtos_tsk->ilockdep_lock->depth >= CONFIG_DEBUG_ILOCKDEP_NUM) {
		raw_local_irq_restore(flags);
		return;
	}
	for (i = 0; i < CONFIG_DEBUG_ILOCKDEP_NUM; i++) {
		if (rtos_tsk->ilockdep_lock->held_locks[i].key == NULL) {
			rtos_tsk->ilockdep_lock->held_locks[i].cpu = smp_processor_id();
			rtos_tsk->ilockdep_lock->held_locks[i].name = lock->name;
			rtos_tsk->ilockdep_lock->held_locks[i].ip = ip;
			rtos_tsk->ilockdep_lock->depth++;
			rtos_tsk->ilockdep_lock->held_locks[i].key = lock_addr;
			break;
		}
	}

	raw_local_irq_restore(flags);
}

void ilockdep_release(struct ilockdep_map *lock, unsigned long ip, void *lock_addr)
{
	unsigned int i;
	unsigned long flags;
	struct task_struct *curr = current;
	struct rtos_task_struct *rtos_tsk = task_to_rtos_task(curr);

	raw_local_irq_save(flags);
	for (i = 0; i < CONFIG_DEBUG_ILOCKDEP_NUM; i++) {
		if (rtos_tsk->ilockdep_lock->held_locks[i].key == lock_addr) {
			rtos_tsk->ilockdep_lock->held_locks[i].key = NULL;
			rtos_tsk->ilockdep_lock->held_locks[i].cpu = NR_CPUS + 1;
			rtos_tsk->ilockdep_lock->held_locks[i].name = NULL;
			rtos_tsk->ilockdep_lock->held_locks[i].ip = 0;
			rtos_tsk->ilockdep_lock->depth--;
			break;
		}
	}

	raw_local_irq_restore(flags);
}

void show_ilockdep_info(struct task_struct *t)
{
	unsigned int i;
	struct ilockdep *ilockdep_lock = NULL;
	struct rtos_task_struct *rtos_tsk;

	if (!t) {
		pr_err("show_ilockdep_info: task is NULL!\n");
		return;
	}
	rtos_tsk = task_to_rtos_task(t);
	ilockdep_lock = rtos_tsk->ilockdep_lock;
	if (ilockdep_lock->locking != NULL)
		pr_err(FORMAT_LOCKING, (char *)(ilockdep_lock->locking),
			ilockdep_lock->locking_name);

	if (ilockdep_lock->depth > 0) {
		pr_err("locked:\n");
		for (i = 0; i < CONFIG_DEBUG_ILOCKDEP_NUM; i++) {
			if (ilockdep_lock->held_locks[i].key != NULL)
				pr_err(FORMAT_LOCKED,
				(char *)(ilockdep_lock->held_locks[i].key),
				ilockdep_lock->held_locks[i].name,
				ilockdep_lock->held_locks[i].cpu,
				(char *)ilockdep_lock->held_locks[i].ip,
				(char *)ilockdep_lock->held_locks[i].ip);
		}
	}
}
EXPORT_SYMBOL(show_ilockdep_info);

static struct ctl_table rtos_ilockdep_sysctls[] = {
	{
		.procname       = "hung_task_show_count",
		.data           = &sysctl_hung_task_show_count,
		.maxlen         = sizeof(unsigned int),
		.mode           = 0640,
		.proc_handler   = proc_hungtask_show_count,
		.extra1         = SYSCTL_ZERO,
	},
#ifdef CONFIG_RTOS_KBOX
	{
		.procname       = "hungtask_log_size",
		.data           = &sysctl_hung_task_log_size,
		.maxlen         = sizeof(unsigned int),
		.mode           = 0640,
		.proc_handler   = proc_hungtask_log_size,
		.extra1         = SYSCTL_ZERO,
	},
#endif /* CONFIG_RTOS_KBOX */
	{}
};

bool ilockdep_unlocking_and_unholding_locks(struct task_struct *t)
{
	struct ilockdep *ilockdep_lock = NULL;
	struct rtos_task_struct *rtos_tsk;

	if (!t) {
		pr_err("ilockdep_unlocking_and_unholding_locks: task is NULL!\n");
		return false;
	}
	rtos_tsk = task_to_rtos_task(t);
	ilockdep_lock = rtos_tsk->ilockdep_lock;
	if (ilockdep_lock->locking == NULL  && ilockdep_lock->depth == 0)
		return true;
	return false;
}
EXPORT_SYMBOL_NS(ilockdep_unlocking_and_unholding_locks, RTOS_DEBUG_KSNAPSHOT);

rtos_kernel_sysctls_proc_init(rtos_ilockdep, rtos_ilockdep_sysctls);
